﻿using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
using Devonline.AspNetCore;
using Devonline.Core;
using Devonline.Entity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace Devonline.AuxiliaryTools.DatabaseTools;

/// <summary>
/// 数据库设计工具
/// </summary>
public class DatabaseDesign
{
    private readonly ILogger<DatabaseDesign> _logger;
    private readonly IExcelExportService _excelExportService;
    public DatabaseDesign(ILogger<DatabaseDesign> logger, IExcelExportService excelExportService)
    {
        _logger = logger;
        _excelExportService = excelExportService;
    }

    /// <summary>
    /// 导出模型列表到 excel 文件
    /// </summary>
    /// <typeparam name="TDbContext">要导出的数据库上下文</typeparam>
    /// <param name="fileName">导出的文件名</param>
    /// <returns></returns>
    public async Task ExportAsync<TDbContext>(string fileName) where TDbContext : DbContext
    {
        try
        {
            var startTime = DateTime.Now;
            _logger.LogWarning($"导出开始, 时间: {startTime.ToString(AppSettings.DEFAULT_DATETIME_FORMAT)}!");
            var types = GetTypesFromDbContext<TDbContext>();
            if (types.Count > 0)
            {
                var index = 0;
                var tables = new List<DataTable>();
                var excelDatas = new List<ExcelData<DataField>>();
                foreach (var type in types)
                {
                    var table = new DataTable
                    {
                        Index = ++index,
                        Name = type.Name,
                        TableName = type.GetTableName(),
                        Description = type.GetDisplayName()
                    };

                    tables.Add(table);
                    excelDatas.Add(new ExcelData<DataField> { Data = GetTableFields(type), SheetName = table.Description });
                }

                var sheetName = typeof(DataTable).GetDisplayName();
                _logger.LogInformation($"正在导出: {sheetName}");
                _excelExportService.Export(new ExcelData<DataTable> { Data = tables, SheetName = sheetName });

                foreach (var excelData in excelDatas)
                {
                    _logger.LogInformation($"正在导出: {excelData.SheetName}");
                    _excelExportService.Export(excelData);
                }

                var file = new FileInfo(fileName);
                using var fileStream = file.OpenWrite();
                await fileStream.WriteAsync(await _excelExportService.GetContentsAsync());
                fileStream.Close();
                _logger.LogWarning($"导出完成, 耗时: {startTime.GetTimeDetail()}!");
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "导出出错, 详情: " + ex.GetMessage());
        }
    }

    /// <summary>
    /// 获取数据库上下文中的数据对象模型的类型
    /// </summary>
    /// <param name="typeName"></param>
    /// <returns></returns>
    private static List<Type> GetTypesFromDbContext<TDbContext>() where TDbContext : DbContext
    {
        var types = new List<Type>();
        var dbset = nameof(DbSet<EntitySet>);
        var propertyInfos = typeof(TDbContext).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.PropertyType.Name.StartsWith(dbset));
        foreach (var propertyInfo in propertyInfos)
        {
            types.Add(propertyInfo.PropertyType.GetGenericArguments().First());
        }

        return types;
    }
    /// <summary>
    /// 按数据对象模型获取数据库表字段列表
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    private static List<DataField> GetTableFields(Type type)
    {
        var fields = new List<DataField>();
        var propertyInfos = type.GetProperties().Where(x => x.PropertyType.IsSampleType() && (!x.HasAttribute<NotMappedAttribute>()));
        if (propertyInfos.Any())
        {
            foreach (var propertyInfo in propertyInfos)
            {
                var index = 0;
                fields.Add(new DataField
                {
                    Index = ++index,
                    Name = propertyInfo.Name,
                    FieldName = propertyInfo.GetColumnName(),
                    DataType = GetDataType(propertyInfo),
                    IsPrimaryKey = propertyInfo.HasAttribute<KeyAttribute>(),
                    IsNullable = GetNullable(propertyInfo),
                    Length = GetLength(propertyInfo),
                    Description = propertyInfo.GetDisplayName()
                });
            }
        }

        return fields;
    }
    /// <summary>
    /// 按字段获取字段描述
    /// </summary>
    /// <param name="propertyInfo"></param>
    /// <returns></returns>
    private static string GetDataType(PropertyInfo propertyInfo)
    {
        if (propertyInfo.PropertyType.IsEnum)
        {
            return "VARCHAR";
        }

        if (propertyInfo.PropertyType == typeof(byte[]))
        {
            return "BLOB";
        }

        return Type.GetTypeCode(propertyInfo.PropertyType) switch
        {
            TypeCode.Boolean or TypeCode.Byte or TypeCode.SByte or TypeCode.Int16 or TypeCode.UInt16 or TypeCode.Int32 or TypeCode.UInt32 or TypeCode.Int64 or TypeCode.UInt64 => "INTEGER",
            TypeCode.Single or TypeCode.Double or TypeCode.Decimal => "FLOAT",
            TypeCode.DateTime => "DATE",
            TypeCode.Char => "CHAR",
            TypeCode.String => "NVARCHAR2",
            _ => propertyInfo.PropertyType.Name
        };
    }
    /// <summary>
    /// 字段是否为可空
    /// </summary>
    /// <param name="propertyInfo"></param>
    /// <returns></returns>
    private static bool GetNullable(PropertyInfo propertyInfo)
    {
        if (propertyInfo.PropertyType == typeof(string) || propertyInfo.PropertyType == typeof(byte[]))
        {
            return !propertyInfo.HasAttribute<RequiredAttribute>();
        }

        return propertyInfo.PropertyType.IsNullable();
    }
    /// <summary>
    /// 获取字段长度
    /// </summary>
    /// <param name="propertyInfo"></param>
    /// <returns></returns>
    private static int? GetLength(PropertyInfo propertyInfo)
    {
        if (propertyInfo.PropertyType == typeof(string))
        {
            return propertyInfo.GetAttributeValue<MaxLengthAttribute, int>(nameof(MaxLengthAttribute.Length));
        }

        if (propertyInfo.PropertyType.IsEnum)
        {
            var column = propertyInfo.GetAttributeValue<ColumnAttribute, string>(nameof(ColumnAttribute.TypeName));
            if (column != null)
            {
                var array = column.Split('(', ')');
                if (array.Length >= 2)
                {
                    return array[1].To<int>();
                }
            }
        }

        return null;
    }
}
